﻿using Swordfish.NET.Collections;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace MyHome.Server.PluginBase
{
    /// <summary>
    /// Abstract base class for all plugins providing some generic implementation.
    /// </summary>
    public abstract class Plugin : IPlugin
    {
        private ConcurrentObservableDictionary<string, string> configuration = new ConcurrentObservableDictionary<string,string>();

        /// <summary>
        /// Initializes a new instance of the <see cref="Plugin"/> class.
        /// </summary>
        protected Plugin()
        {
            Id = -1;
        }

        /// <summary>
        /// Occurs when the configuration has changed.
        /// </summary>
        protected event NotifyCollectionChangedEventHandler ConfigurationChanged;

        /// <summary>
        /// Gets or sets the configuration for this plugin.
        /// </summary>
        /// <value>
        /// The configuration to use.
        /// </value>
        public IDictionary<string, string> Configuration
        {
            get
            {
                return configuration;
            }
            set
            {
                if (configuration != null)
                    configuration.CollectionChanged -= configuration_CollectionChanged;

                var dict = value as ConcurrentObservableDictionary<string, string>;
                if (dict != null)
                    configuration = dict;
                else if (value != null)
                    configuration = new ConcurrentObservableDictionary<string, string>(value);
                //else
                //    configuration = null;

                if (configuration != null)
                    configuration.CollectionChanged += configuration_CollectionChanged;
            }
        }

        /// <summary>
        /// Gets or sets the unique identifier.
        /// </summary>
        /// <value>
        /// The unique identifier.
        /// </value>
        public int Id { get; set; }

        /// <summary>
        /// Gets the type of the plugin.
        /// </summary>
        /// <value>
        /// The type of the plugin.
        /// </value>
        public abstract PluginType PluginType { get; }

        /// <summary>
        /// Gets the state of the plugin.
        /// </summary>
        /// <value>
        /// The state.
        /// </value>
        public PluginState State { get; protected set; }

        /// <summary>
        /// Gets or sets the x coordinate of any visible representation of the plugin.
        /// </summary>
        /// <value>
        /// The x coordinate.
        /// </value>
        public int X { get; set; }

        /// <summary>
        /// Gets or sets the y coordinate of any visible representation of the plugin.
        /// </summary>
        /// <value>
        /// The y coordinate.
        /// </value>
        public int Y { get; set; }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object" }, is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            var plugin = obj as IPlugin;
            if (plugin == null)
                return false;

            return Id == plugin.Id;
        }

        /// <summary>
        /// Determines whether the specified <see cref="IPlugin" }, is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="IPlugin" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="IPlugin" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public bool Equals(IPlugin obj)
        {
            if (obj == null)
                return false;

            return Id == obj.Id;
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return Id;
        }

        /// <summary>
        /// Tries to start this plugin asynchronous.
        /// </summary>
        /// <returns>
        /// A value indicating the success of this operation
        /// </returns>
        public async virtual Task<bool> StartAsync()
        {
            Trace.TraceInformation("{0} \"{1}\", Id: {2} is starting", PluginType.ToString(), this, Id);
            State = PluginState.Starting;

            try
            {
                await Task.Run(() => OnStart());
                Trace.TraceInformation("{0} \"{1}\", Id: {2} is running", PluginType.ToString(), this, Id);
                State = PluginState.Running;
                return true;
            }
            catch
            {
                Trace.TraceError("{0} \"{1}\", Id: {2} failed to start", PluginType.ToString(), this, Id);
                State = PluginState.Error;
                return false;
            }
        }

        /// <summary>
        /// Tries to stop this plugin asynchronous.
        /// </summary>
        /// <returns>
        /// A value indicating the success of this operation
        /// </returns>
        public async virtual Task<bool> StopAsync()
        {
            Trace.TraceInformation("{0} \"{1}\", Id: {2} is stopping", PluginType.ToString(), this, Id);
            State = PluginState.Stopping;

            try
            {
                await Task.Run(() => OnStop());
                Trace.TraceInformation("{0} \"{1}\", Id: {2} is stopped", PluginType.ToString(), this, Id);
                State = PluginState.Stopped;
                return true;
            }
            catch
            {
                Trace.TraceError("{0} \"{1}\", Id: {2} failed to stop", PluginType.ToString(), this, Id);
                State = PluginState.Error;
                return false;
            }
        }

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return "{" + PluginType.ToString() + "\"" + GetType().Name + "\", Id: " + Id + "}";
        }

        /// <summary>
        /// Called when the plugin is starting.
        /// Place any initialization code here.
        /// </summary>
        protected abstract void OnStart();

        /// <summary>
        /// Called when the plugin is stopping.
        /// Place any disposing code here.
        /// </summary>
        protected abstract void OnStop();

        private void configuration_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            var handler = ConfigurationChanged;
            if (handler != null)
                handler(this, e);
        }
    }
}